/*
 * Copyright 2018 dc-square GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.hivemq.extensions.rbac.listener;

import com.hivemq.extensions.rbac.model.Constant;
import com.hivemq.extensions.rbac.model.Device;
import com.hivemq.extensions.rbac.model.ProductDevieAuthCheck;
import com.hivemq.extensions.rbac.utils.CredentialsValidator;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.extension.sdk.api.auth.SimpleAuthenticator;
import com.hivemq.extension.sdk.api.auth.parameter.SimpleAuthInput;
import com.hivemq.extension.sdk.api.auth.parameter.SimpleAuthOutput;
import com.hivemq.extension.sdk.api.auth.parameter.TopicPermission;
import com.hivemq.extension.sdk.api.packets.auth.DefaultAuthorizationBehaviour;
import com.hivemq.extension.sdk.api.packets.connect.ConnackReasonCode;
import com.qjava.qsql.redis.RedisPoolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

/**
 * 身份验证器
 * @author MI
 */
public class FileAuthAuthenticator implements SimpleAuthenticator {

    private static final Logger log = LoggerFactory.getLogger(FileAuthAuthenticator.class);
    private static final String USER_ROLE = "product_defaut";
    private static final ByteBuffer USER_ROLE_PWD = ByteBuffer.wrap("123456".getBytes());


    /** 通配符过滤 **/
    private String[] wildcards = {"#", "+"};

    /**
     * 身份验证器
     */
    private final @NotNull
    CredentialsValidator credentialsValidator;

    public FileAuthAuthenticator(final @NotNull CredentialsValidator credentialsValidator) {
        this.credentialsValidator = credentialsValidator;
    }

    /**
     * 连接
     *    产品key:用户名
     *    产品秘钥:密码
     *    设备秘钥:客户端id
     * @param simpleAuthInput
     * @param simpleAuthOutput
     */
    @Override
    public void onConnect(@NotNull final SimpleAuthInput simpleAuthInput, @NotNull final SimpleAuthOutput simpleAuthOutput) {

        final Optional<String> userNameOptional = simpleAuthInput.getConnectPacket().getUserName();
        final Optional<ByteBuffer> passwordOptional = simpleAuthInput.getConnectPacket().getPassword();

        if (!userNameOptional.isPresent() || !passwordOptional.isPresent()) {
            simpleAuthOutput.failAuthentication(ConnackReasonCode.BAD_USER_NAME_OR_PASSWORD, "验证失败，因为缺少用户名或密码");
            return;
        }

        // 用户名
        final String userName = userNameOptional.get();
        // 这里的clientId对设备key
        final String clientId = simpleAuthInput.getClientInformation().getClientId();
        // 这里的登陆密码,对照产品的秘钥
        // final String userSecret = StandardCharsets.UTF_8.decode(passwordOptional.get()).toString() ;

        if (clientId.contains("#") || clientId.contains("+")) {
            simpleAuthOutput.failAuthentication(ConnackReasonCode.CLIENT_IDENTIFIER_NOT_VALID, "客户端标识符中不允许使用字符“＃”和“ +”");
            return;
        }

        if (userName.contains("#") || userName.contains("+")) {
            simpleAuthOutput.failAuthentication(ConnackReasonCode.BAD_USER_NAME_OR_PASSWORD, "用户名中不允许使用字符“＃”和“ +”");
            return;
        }

        AuthRntry authRntry = null;
        // 先检查系统用户
        authRntry = onAdminAuth( simpleAuthOutput, simpleAuthInput);
        if(authRntry == null){
            // 再检查是否是普通用户
            authRntry = onUserDeviceAuth(simpleAuthOutput, simpleAuthInput);
        }

        if(authRntry == null){
            return;
        }

        // 添加权限到授权器
        simpleAuthOutput.getDefaultPermissions().addAll(authRntry.topicPermissions);
        simpleAuthOutput.getDefaultPermissions().setDefaultBehaviour(DefaultAuthorizationBehaviour.DENY);
        simpleAuthOutput.authenticateSuccessfully();

        // 添加redis: 设备=用户名,有效时间在配置文件里面
        RedisPoolUtil.set(Constant.REDIS_NAME + authRntry.device.getDevice_key(), authRntry.device.getProduct_key());
       // log.debug("设备连接成功,设备id:{}", clientId);
    }


    /**
     * 系统账号认证
     * @return
     */
    private AuthRntry onAdminAuth(final SimpleAuthOutput simpleAuthOutput,
                                  final SimpleAuthInput simpleAuthInput ){

        final Optional<String> userNameOptional = simpleAuthInput.getConnectPacket().getUserName();
        final Optional<ByteBuffer> passwordOptional = simpleAuthInput.getConnectPacket().getPassword();

        final List<String> roles = credentialsValidator.getRoles(userNameOptional.get(), passwordOptional.get());

        if (roles == null || roles.isEmpty()) {

            return null;
        }

        String clientId = simpleAuthInput.getClientInformation().getClientId();
        // 默认为系统用户
        String product = "system";

        // 适配权限集
        List<TopicPermission> topicPermissions = credentialsValidator.getPermissions(clientId, product, roles);

        Device device = new Device();
        device.setDevice_key(clientId);
        device.setProduct_key(product);
        /** 标记客户连接属性为 system */
        simpleAuthInput.getClientInformation().setGruopId(product);
        simpleAuthInput.getClientInformation().setTags(product);
        //log.debug("系统管理员登陆:{}-{}", userNameOptional.get(), clientId);
        return new AuthRntry(device, topicPermissions);
    }


    /**
     * 用户设备验证
     */
    private AuthRntry onUserDeviceAuth(final SimpleAuthOutput simpleAuthOutput,
                                       final SimpleAuthInput simpleAuthInput ){

        final Optional<String> userNameOptional = simpleAuthInput.getConnectPacket().getUserName();
        final Optional<ByteBuffer> passwordOptional = simpleAuthInput.getConnectPacket().getPassword();
        // 用户名
        final String userName = userNameOptional.get();
        // 这里的clientId对设备key
        final String clientId = simpleAuthInput.getClientInformation().getClientId();
        // 这里的登陆密码,对照产品的秘钥
        final String userSecret = StandardCharsets.UTF_8.decode(passwordOptional.get()).toString() ;
        // 数据库设备-产品验证器
        ProductDevieAuthCheck productDevieAuthCheck = new ProductDevieAuthCheck();

        // 检查设备关系是否存在
        // 产品key:用户名
        // 产品秘钥:密码
        // 设备秘钥:客户端id
        Device device = productDevieAuthCheck.ckeck(userName, userSecret, clientId);
        if(device == null){
            log.debug("密码错误,设备id:{}, 用户:{}, 用户秘钥:{}", clientId, userName, userSecret);
            simpleAuthOutput.failAuthentication(ConnackReasonCode.BAD_USER_NAME_OR_PASSWORD, "验证失败，密码错误");
            return null;
        }
        simpleAuthInput.getClientInformation().setGruopId(device.getProduct_key());
        simpleAuthInput.getClientInformation().setTags(device.getGroup_key());

        // 防止带有MQTT通配符的clientId和用户名
        for(String wildcard : wildcards){
            if (clientId.contains(wildcard) || userName.contains(wildcard)) {
                // 客户端未通过身份验证
                log.debug("客户端未通过身份验证, 防止带有MQTT通配符的设备标识和用户名");
                simpleAuthOutput.failAuthentication(ConnackReasonCode.CLIENT_IDENTIFIER_NOT_VALID, "客户端标识符中不允许使用字符'＃'和'+'");
                return null;
            }
        }

        // 获取角色组
        final List<String> roles = credentialsValidator.getRoles(USER_ROLE, USER_ROLE_PWD);
        if (roles == null || roles.isEmpty()) {
            log.debug("角色组获取失败");
            simpleAuthOutput.failAuthentication(ConnackReasonCode.NOT_AUTHORIZED, "身份验证失败，因为凭据无效");
            return null;
        }

        // 适配权限集
        List<TopicPermission> topicPermissions = credentialsValidator.getPermissions(clientId, device.getProduct_key(), roles);

        return new AuthRntry(device, topicPermissions);
    }

    class AuthRntry{
        // 设备
        Device device;
        // 权限集
        List<TopicPermission> topicPermissions;

        public AuthRntry(Device device, List<TopicPermission> topicPermissions) {
            this.device = device;
            this.topicPermissions = topicPermissions;
        }
    }
}
